#ifndef MESSAGE_QUEUE_H_
#define MESSAGE_QUEUE_H_

#include <sstream>
#include <cstdio>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include "../common/Exception.h"
#include "../common/StringUtils.h"
#include "../logger/logger.h"

using namespace std;

namespace ipc
{

#define MTYPE_ANY 0
#define MQ_PERMS 0666

typedef long MessageId_t;

//For data alignment purposes (32/64 bit compatibility)
#pragma pack(push, 1)
template<class T>
struct MQMessage
{
	long mtype;     // Esto nunca hay q olvidarse de ponerlo!
	T data;
};
#pragma pack(pop)

template<class T> class MessageQueue
{
private:
    int m_id;
    std::string m_fileName;
    char m_key;

    /**
     * Constructor (privado)
     *
     * @param id Id de la cola
     */
    MessageQueue( int id , std::string filename,char key);

public:

    MessageQueue();

    /**
     * Destructor
     */
    ~MessageQueue();

    /**
     * Poner un mensaje en la cola
     *
     * @param mtype Tipo del mensaje
     * @param msg   Mensaje
     */
    void put( MessageId_t mtype, T msg );

    /**
     * Sacar un mensaje de la cola
     *
     * @param mtype Tipo del mensaje
     *
     * @return El Mensaje
     */
    T get( MessageId_t tipo );

    /**
     * Metodo para crear una Message Queue
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     * @param semAmmount Cantidad de elementos en el array
     * @param flags flags del array (no tocar)
     *
     * @return La Message Queue creada
     */
    static MessageQueue<T> create( std::string file, int charKey,
            int flags = IPC_CREAT | IPC_EXCL );

    /**
     * Metodo para crear no exclusivo de una Message Queue
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     * @param semAmmount Cantidad de elementos en el array
     *
     * @return La Message Queue creada
     */
    static MessageQueue<T> createNonExclusive( std::string file, int charKey);

    /**
     * Metodo para obtener una Message Queue (ya creada)
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     *
     * @return La Message Queue
     */
    static MessageQueue<T> get( std::string file, int charKey );

    /**
     * Metodo para destruir una Message Queue
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     */
    static void destroy( std::string file, int charKey );

    /**
     * Metodo para destruir una Message Queue, si no existe no tiene efecto
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     */
    static void destroyNonExclusive( std::string file, int charKey );
};

template<class T>
inline MessageQueue<T>::MessageQueue(){}

template<class T>
inline MessageQueue<T>::MessageQueue( int id , std::string filename,char key) :
        m_id( id ), m_fileName(filename),m_key(key)
{
}

template<class T>
inline MessageQueue<T>::~MessageQueue()
{
}

template<class T>
inline void MessageQueue<T>::put( MessageId_t mtype, T msg )
{
    std::string error;
    MQMessage<T> message;
    message.mtype = mtype;
    message.data = msg;

    if ( msgsnd( this->m_id, ( const void * ) &message,
            sizeof(MQMessage<T> ) - sizeof(long), 0 ) < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error writing the message queue "+m_fileName +" :"+m_key +":"+ error );
    }
}

template<class T>
inline T MessageQueue<T>::get( MessageId_t tipo )
{
    std::string error;
    MQMessage<T> message;
    if ( msgrcv( this->m_id, ( void * ) &message,
            sizeof(MQMessage<T> ) - sizeof(long), tipo, 0 ) < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error reading the message queue "+m_fileName +" :"+StringUtils::NumberToString(m_key) +":"+ error );
    }
    return message.data;
}

template<class T>
inline MessageQueue<T> MessageQueue<T>::create( std::string file, int charKey,
        int flags )
{
    std::string error;
    std::string touchCmd;
    int key;
    int id;
    touchCmd = "touch " + file;
    system( touchCmd.c_str() );
    key = ftok( file.c_str(), charKey );
    if ( key < 0 )
    {
        error = strerror( errno );
        throw Exception(
                "Error in the message queue key on file: " + file + ". Error: "
                        + error );
    }
    flags |= MQ_PERMS;
    id = msgget( key, flags );

    CLogger::log("Creando MQUEUE " + file + " " + (char)key, LogLevelEnum::WARN);
    if ( id < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error creating the message queue: " + file+" " +error );
    }
    return MessageQueue<T>( id , file,key);
}

template<class T>
inline MessageQueue<T> MessageQueue<T>::createNonExclusive( std::string file, int charKey) {
    return MessageQueue<T>::create( file, charKey, IPC_CREAT );
}

template<class T>
inline MessageQueue<T> MessageQueue<T>::get( std::string file, int charKey )
{
	return MessageQueue<T>::create( file, charKey, IPC_EXCL );
}

template<class T>
inline void MessageQueue<T>::destroyNonExclusive( std::string file, int charKey )
{
    try {
    	MessageQueue<T> mq = MessageQueue<T>::get( file, charKey );
    	msgctl( mq.m_id, IPC_RMID, NULL );
    } catch(...) {};
}

template<class T>
inline void MessageQueue<T>::destroy( std::string file, int charKey )
{
	std::string error;
    MessageQueue<T> mq = MessageQueue<T>::get( file, charKey );
    if ( msgctl( mq.m_id, IPC_RMID, NULL ) )
    {
        error = strerror( errno );
        throw Exception(
                "Error while destroying the message queue. Error: " + error );
    }
}

} /* namespace ipc */
#endif /* MESSAGE_QUEUE_H_ */
